package org.singledog.permission.core;

import lombok.extern.slf4j.Slf4j;
import org.singledog.permission.utils.JwtUtils;
import org.singledog.permission.utils.TokenBean;

import java.security.Permission;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Permission上下文
 *
 * @author liuzheming
 * @since 2021.9.14
 */
@Slf4j
public class PermissionApplicationContext {

    /**
     * 用于存放配置文件中的权限
     */
    private static final Map<String, PermissionSource> PERMISSION_MAP = new ConcurrentHashMap<>();

    /**
     * 用于存放请求路径对应的权限
     */
    private static final Map<String, List<PermissionSource>> PERMISSION_MAP_URI = new ConcurrentHashMap<>();


    /**
     * 用于存放用户token
     */
    public static final Map<String, TokenBean> userMap = new ConcurrentHashMap<>();

    /**
     * 添加权限
     *
     * @param permission 权限名称
     */
    public void addPermission(String permission) {
        if (!PERMISSION_MAP.containsKey(permission)) {
            PERMISSION_MAP.put(permission, new PermissionSource(permission));
        }
    }

    /**
     * 删除权限
     *
     * @param permission 权限名称
     */
    public void deletePermission(String permission) {
        PERMISSION_MAP.remove(permission);
    }

    /**
     * 获得权限
     *
     * @param permission 权限名称
     * @return 具体的权限
     */
    public Permission getPermission(String permission) {
        return PERMISSION_MAP.get(permission);
    }

    /**
     * 获得具体路径的权限
     *
     * @param uri 路径
     * @return 具体的权限
     */
    public static List<PermissionSource> getPermissionUri(String uri) {
        return PERMISSION_MAP_URI.get(uri);
    }

    /**
     * 添加具体路径的权限
     *
     * @param uri         路径
     * @param permissions 权限
     */
    public static void addPermissionUri(String uri, List<PermissionSource> permissions) {
        if (!PERMISSION_MAP_URI.containsKey(uri)) {
            PERMISSION_MAP_URI.put(uri, permissions);
        }
    }

    /**
     * 定时任务删除过期token
     */
    private void executeSchedule() {
        log.info("schedule start...");
        long currentTimeMillis = System.currentTimeMillis();
        for (String userId : PermissionApplicationContext.userMap.keySet()) {
            TokenBean tokenBean = PermissionApplicationContext.userMap.get(userId);
            Map<String, Map<String, Long>> ipToken = tokenBean.getIpToken();
            if(ipToken.size() == 0) {
                PermissionApplicationContext.userMap.remove(userId);
            } else {
                for (String ip : ipToken.keySet()) {
                    Map<String, Long> tokenTimeMap = ipToken.get(ip);
                    if(tokenTimeMap.size() == 0) {
                        ipToken.remove(ip);
                    } else {
                        for (String token : tokenTimeMap.keySet()) {
                            if((currentTimeMillis - tokenTimeMap.get(token) > JwtUtils.getExpireTime())) {
                                tokenTimeMap.remove(token);
                            }
                        }
                    }
                }
            }
        }
        log.info("schedult end ...");
    }

    public void schedule(long period) {
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                executeSchedule();
            }
        };
        Timer timer = new Timer();
        timer.schedule(task,0L,period);
    }

    private static Date buildTime() {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.HOUR_OF_DAY, 1);
        calendar.set(Calendar.MINUTE, 0);
        calendar.set(Calendar.SECOND, 0);
        Date time = calendar.getTime();
        if (time.before(new Date())) {
            //若果当前时间已经是凌晨1点后，需要往后加1天，否则任务会立即执行。
            //很多系统往往系统启动时就需要立即执行一次任务，但下面又需要每天凌晨1点执行，怎么办呢？
            //很简单，就在系统初始化话时单独执行一次任务（不需要用定时器，只是执行那段任务的代码）
            time = addDay(time, 1);
        }
        return time;
    }

    private static Date addDay(Date date, int days) {
        Calendar startDT = Calendar.getInstance();
        startDT.setTime(date);
        startDT.add(Calendar.DAY_OF_MONTH, days);
        return startDT.getTime();
    }
}
